#pragma once
#include<iostream>
#include<QReadWriteLock>
#include<QMutex>
#include<unordered_map>
#include<thread>
#include<atomic>
#include"MemUtil.h"
using namespace std;

enum LOCKTYPE{NOLOCK, READ, WRITE};

class ThreadLockTypeMap{

    unordered_map<thread::id, LOCKTYPE> map;

    QReadWriteLock readWriteLock;

    thread::id getThreadId(){
        return this_thread::get_id();
    }

public:
    LOCKTYPE getThreadLockType(){
        readWriteLock.lockForRead();
        const auto& it = map.find(getThreadId());
        readWriteLock.unlock();
        return it == map.end() ? NOLOCK : it->second; //由于依据线程号获取，所以不会被人修改 在同步代码外也没关系
    }

    void setThreadLockType(const LOCKTYPE& lockType){
        readWriteLock.lockForWrite();
        map[getThreadId()] = lockType;
        readWriteLock.unlock();
    }
};


//支持锁升级的可重入读写锁（锁升级有失败风险）
class ResourceLock{        //在正常使用时可以保证安全。
    QReadWriteLock* lock;
    ThreadLockTypeMap threadLockTypeMap;         //标记线程号对应的锁类型
    atomic_int16_t flag;                           //用于标记在升级锁的过程中有没有被别的线程获取写锁

public:

    ResourceLock():lock(new QReadWriteLock()){}

    ~ResourceLock(){  MemUtil::clearPtrMem(lock); }

    bool tryWriteLockAndProcessFlag(const int& timeOut){
        bool isGet = lock->tryLockForWrite(timeOut);
        if(isGet){
            flag.fetch_add(1);
        }
        return isGet;
    }

    //没有获取到锁，返回false 需要解开原本的锁
    //ThreadLockTypeMap使用线程号区分， 一个线程同时只会进入一个函数， 因此不需要同步， 只需要在ThreadLockTypeMap层面保证操作安全。
    bool tryReadLock(const int& timeOut = 0){//即在ThreadLockTypeMap的两次操作之间没有线程会改当前线程号的锁类型
        LOCKTYPE lockType = threadLockTypeMap.getThreadLockType();
        if(lockType == NOLOCK){
            bool isGet = lock->tryLockForRead(timeOut);
            if(isGet)
                threadLockTypeMap.setThreadLockType(READ);
            return isGet;
        }//已获得写锁上读锁时不操作 不支持锁降级
        return true;
    }

    //没有获取到锁，返回false 需要解开原本的锁
    //ThreadLockTypeMap使用线程号区分， 因此不需要同步， 只需要在ThreadLockTypeMap层面保证操作安全。
    bool tryWriteLock(const int& timeOut = 0){//即在ThreadLockTypeMap的两次操作之间没有线程会改当前线程号的锁类型
        LOCKTYPE lockType = threadLockTypeMap.getThreadLockType();
        if(lockType != WRITE){//支持锁升级，先解锁再加锁。
            if(lockType == READ){
                flag.store(0);  //先置0再解锁
                lock->unlock();
                bool isGet = tryWriteLockAndProcessFlag(timeOut);            //获取写锁成功flag++；
                bool isInsert = (flag.load() != 1);                        //严格flag==1才成功
                if(isGet && isInsert){
                    lock->unlock();//如果成功获取到锁，但是中间有人插入获取到锁（没有真正成功），则解锁。
                    isGet = false;  //设为没有获取到锁
                }
                if(isGet){
                    threadLockTypeMap.setThreadLockType(WRITE);
                    return true;
                }
            }
            else {                  //无锁状态
                if(tryWriteLockAndProcessFlag(timeOut)){
                    threadLockTypeMap.setThreadLockType(WRITE);
                    return true;
                }
            }
            threadLockTypeMap.setThreadLockType(NOLOCK);
            return false;
        }
        return true;
    }

    //ThreadLockTypeMap使用线程号区分， 一个线程同时只会进入一个函数， 因此不需要同步， 只需要在ThreadLockTypeMap层面保证操作安全。
    void releaseResource(){
        if(threadLockTypeMap.getThreadLockType() != NOLOCK){
            lock->unlock();
            threadLockTypeMap.setThreadLockType(NOLOCK);
        }
    }

};



class LockUtil{
public:
    static bool tryLock(ResourceLock* lock, const LOCKTYPE& lockType, const int& timeOut){
        bool isGet = false;
        if(lockType == READ){
            isGet = lock->tryReadLock(timeOut);
        }
        else if(lockType == WRITE){
            isGet = lock->tryWriteLock(timeOut);
        }
        else{
            throw string("locktype error");
        }
        return isGet;
    }


    static bool tryLock(QReadWriteLock* lock, const LOCKTYPE& lockType, const int& timeOut){
        bool isGet = false;
        if(lockType == READ){
            isGet = lock->tryLockForRead(timeOut);
        }
        else if(lockType == WRITE){
            isGet = lock->tryLockForWrite(timeOut);
        }
        else{
            throw string("locktype error");
        }
        return isGet;
    }


    static void getLock(QReadWriteLock* lock, const LOCKTYPE& lockType){
        if(lockType == READ){
            lock->lockForRead();
        }
        else if(lockType == WRITE){
            lock->lockForWrite();
        }
        else{
            throw string("locktype error");
        }
    }


    static void getLock(ResourceLock* lock, const LOCKTYPE& lockType, const int& timeOut){
        if(!tryLock(lock, lockType, timeOut)){
            throw string("deadLock error");
        }
    }


    static void getLock(QReadWriteLock* lock, const LOCKTYPE& lockType, const int& timeOut){
        if(!tryLock(lock, lockType, timeOut)){
            throw string("deadLock error");
        }
    }

};



